{%macro ToPythonType(schemaType)-%}
{%-if schemaType == 'string'-%}
str
{%-elif schemaType == 'integer'-%}
int
{%-elif schemaType == 'number'-%}
float
{%-elif schemaType == 'boolean'-%}
bool
{%-endif-%}
{%-endmacro-%}

{%-macro FormatValue(schemaType, value)%}
{%-if schemaType == 'string'-%}"{{value}}"
{%-elif schemaType == 'boolean'-%}
    {%-if value or value == 'true'%}True{%endif-%}
    {%-if not value or value == 'false'%}False{%endif-%}
{%-else-%}
    {{-value-}}
{%-endif-%}
{%-endmacro%}

{%-macro ValidateValue(schema, varName)%}
if not isinstance({{varName}}, {{ToPythonType(schema.type)}}):
    raise ValueError("Passed value '{}' was not a {{schema.type}}".format({{varName}}))
{%-if schema.format is defined%}
{%-if schema.format.startswith('uint')%}
if {{varName}} < 0:
    raise ValueError("Value '{}' is signed but the format is {}".format({{varName}}, "{{schema.format}}"))
{%-if schema.format.endswith('32')%}
if {{varName}} > 0xFFFFFFFF:
    raise ValueError("Value '{}' is greater than the max for {}".format({{varName}}, "{{schema.format}}"))
{%-endif%} {#format is uint32#}
{%-if schema.format.endswith('16')%}
if {{varName}} > 0xFFFF:
    raise ValueError("Value '{}' is greater than the max for {}".format({{varName}}, "{{schema.format}}"))
{%-endif%} {#format is uint16#}
{%-if schema.format.endswith('8')%}
if {{varName}} > 0xFF:
    raise ValueError("Value '{}' is greater than the max for {}".format({{varName}}, "{{schema.format}}"))
{%-endif%} {#format is uint8#}
{%-elif schema.format.startswith('int')%}
{%-if schema.format.endswith('32')%}
if {{varName}} > 0x7FFFFFFF or {{varName}} < (-1*0x800000000):
    raise ValueError("Value '{}' is outside the range for {}".format({{varName}}, "{{schema.format}}"))
{%-endif%} {#format is int32#}
{%-if schema.format.endswith('16')%}
if {{varName}} > 0x7FFF or {{varName}} < (-1*0x80000):
    raise ValueError("Value '{}' is outside the range for {}".format({{varName}}, "{{schema.format}}"))
{%-endif%} {#format is int16#}
{%-if schema.format.endswith('8')%}
if {{varName}} > 0x7F or {{varName}} < (-1*0x800):
    raise ValueError("Value '{}' is outside the range for {}".format({{varName}}, "{{schema.format}}"))
{%-endif%} {#format is int8#}
{%-endif%} {#format is int/uint#}
{%-endif%} {#format#}
{%-if schema.maxLength is defined%}
if len({{varName}}) > {{schema.maxLength}}:
    raise ValueError("Value '{}' length {} is greater than maximum of {{schema.maxLength}}".format({{varName}}, len({{varName}})))
{%-endif%} {#maxLength#}
{%-if schema.minLength is defined%}
if len({{varName}}) < {{schema.minLength}}:
    raise ValueError("Value '{}' length {} is less than minimum of {{schema.minLength}}".format({{varName}}, len({{varName}})))
{%-endif%} {#minLength#}
{%-if schema.minimum is defined%}
if {{varName}} < {{schema.minimum}}:
    raise ValueError("Value '{}' is less than the minimum of {{schema.minimum}}".format({{varName}}))
{%-endif%} {#minimum#}
{%-if schema.maximum is defined%}
if {{varName}} > {{schema.maximum}}:
    raise ValueError("Value '{}' is more than the maximum of {{schema.maximum}}".format({{varName}}))
{%-endif%} {#maximum#}
{%-if schema.exclusiveMinimum is defined%}
if {{varName}} <= {{schema.exclusiveMinimum}}:
    raise ValueError("Value '{}' is less than the minimum of {{schema.exclusiveMinimum}}".format({{varName}}))
{%-endif%} {#minimum#}
{%-if schema.exclusiveMaximum is defined%}
if {{varName}} >= {{schema.exclusiveMaximum}}:
    raise ValueError("Value '{}' is more than the maximum of {{schema.exclusiveMaximum}}".format({{varName}}))
{%-endif%} {#maximum#}
{%-if schema.multipleOf is defined%}
if ({{varName}} % {{schema.multipleOf}}) != 0:
    raise ValueError("Value '{}' is not a multiple of {{schema.multipleOf}}".format({{varName}}))
{%-endif%} {#maximum#}
{%-if schema.enum is defined%}
acceptable_values = [{%for v in schema.enum%}{{FormatValue(schema.type, v)}}, {%endfor%}]
if {{varName}} not in acceptable_values:
    raise ValueError("Value '{}' is not in the list of acceptable value: {}".format({{varName}}, acceptable_values))
{%-endif%} {#enum#}
{%-if schema.const is defined%}
if {{varName}} != {{schema.const | quotestring(schema.type)}}:
    raise ValueError("Value '{}' is not the constant value '{{schema.const}}'".format({{varName}}))
{%-endif%} {#enum#}
{%-if schema.pattern is defined%}
pattern = r"{{schema.pattern}}"
if re.match(pattern, {{varName}}) is None:
    raise ValueError("Value '{}' did not match the established regular expression".format({{varName}}))
{%-endif%}
{%-endmacro-%}

{%-set fullClassPath -%}
    {%-if parentClass is not defined or parentClass is none-%}
        {{Name}}
    {%-else-%}
        {{parentClass}}.{{Name}}
    {%-endif-%}
{%-endset-%}

class {{Name}}(object):
    """ This class is a schema-validating wrapper around a {{schema.type}}.
    """

    def __init__(self, value{%if schema.default is defined%}={{FormatValue(schema.type, schema.default)}}{%endif%}):
        self.Set(value)

    @staticmethod
    def _Validate(value):
        """Ensures that the provided {{schema.type}} value meets all the schema constraints.
        """
        {{-ValidateValue(schema, 'value') | indent(8)}}

    def Set(self, new_value) -> {{fullClassPath}}:
        if isinstance(new_value, type(self)):
            self._value = new_value._value
        elif isinstance(new_value, {{ToPythonType(schema.type)}}):
            self._Validate(new_value)
            self._value = new_value
        {%if schema.type == 'number' -%}
        elif isinstance(new_value, int):
            self._Validate(float(new_value))
            self._value = float(new_value)
        {%endif-%}
        else:
            raise TypeError("The provided type was not a {{fullClassPath}} or a {{ToPythonType(schema.type)}}")
        return self

    def Get(self) -> {{ToPythonType(schema.type)}}:
        return self._value

    def Serializable(self) -> {{ToPythonType(schema.type)}}:
        return self.Get()

